import org.codehaus.groovy.grails.commons.ApplicationHolder
import org.codehaus.groovy.grails.commons.GrailsClassUtils

/**
 * The AuthService is the central class in the Authorize plugin. It
 * performs the actual authentication and authorization of the user.
 * @author: Darryl Pentz
 * @since: 2007/10/06
 */
class AuthService {

    def setup() {
        // Setup all roles from the database as a map
        def roles = AuthRole.list()
        def roleMap = [:] as Map

        // We don't map these into the database
        def reservedControllers = ["action", "user", "controler", "role"]

        for (role in roles) {
            roleMap[role.name] = role
        }

        def application = ApplicationHolder.application

        // Get the list of classes
        def classes = application.controllerClasses
        for (cls in classes) {
            // Get its short name
            def ctrlName = cls.logicalPropertyName
            if (ctrlName in reservedControllers) {
                log.debug("Controller '${ctrlName}' is reserved - skipping")
                continue;
            }

            Map actionMap = null;
            boolean allProtected = false;
            // Get the protectedAccess attribute from the controller class
            Object obj = GrailsClassUtils.getStaticPropertyValue(cls.getClazz(), "authorize");
            if (obj) {
                log.debug("Controller '${ctrlName}' authorize found : ${obj}");
                if (obj instanceof String) {
                    log.debug("All actions to be mapped to ${obj}")
                    actionMap = ["all": [obj]]
                } else if (obj instanceof List) {
                    actionMap = ["all": obj]
                } else if (obj instanceof Map) {
                    actionMap = obj as Map
                }
            } else {
                log.debug("Controller '${ctrlName}' not protected - skipping")
                continue;
            }

            // Create a set of all the actions for a controller using the uri to get the closure name
            def actions = [] as Set;
            for (uri in cls.uRIs) {
                actions.add(cls.getClosurePropertyName(uri))
            }

            // Define a list of roles to apply to all actions, if one was declared
            def allList = actionMap["all"]; // list of String roles to apply to all actions

            // Create the controller record
            AuthControler ctrl = new AuthControler(name: ctrlName)
            // For each unique action, see if it must be linked to a role
            for (actionName in actions) {
                def actionRoles = []
                def rolesForThisAction = actionMap[actionName] // list of String roles to apply to just this action
                if (!rolesForThisAction) {
                    rolesForThisAction = []
                }

                AuthAction action = new AuthAction(name: actionName);
                if (allList) {
                    for (aRole in allList) {
                        def role = roleMap[aRole]
                        if (role) {
                            log.debug("Adding role '${role.name}' to actionRoles")
                            actionRoles.add(role)
                        }
                        rolesForThisAction.remove(aRole) // no need to do it again later
                    }
                }

                if (rolesForThisAction) {
                    for (aRole in rolesForThisAction) {
                        def role = roleMap[aRole]
                        if (role) {
                            log.debug("Adding role '${role.name}' to actionRoles")
                            actionRoles.add(role)
                        }
                    }
                }

                if (actionRoles.size() > 0) {
                    log.debug("Adding action '${actionName}' to controller '${ctrlName}'")
                    ctrl.addToActions(action)
                    actionRoles.each { role ->
                        log.info("Adding role '${role.name}' to '${ctrlName}:${actionName}'")
                        action.addToRoles(role)
                    }
                    action.controler = ctrl
                    ctrl.save()
                }
            }
        }
    }

    def tearDown() {
        def controllers = AuthControler.list()
        // cascading deletes should get rid of associated actions
        controllers.each {controller ->
            controller.delete();
        }
    }

    def authorize(String username, String ctrl, String actionName) {
        AuthUser user = AuthUser.findByUsername(username)
        return authorize(user, ctrl, actionName)
    }

    // Main authorization method to determine whether a 
    def authorize(AuthUser user, String ctrl, String actionName) {
        // We want to determine whether this controller action requires any
        // specific roles.
        AuthControler controller = AuthControler.findByName(ctrl)
        if (!controller) {
            log.warn("Authorization requested for controller ${ctrl} but no controller by that name")
            return true; // we will return true - if it ain't in the system it can't be important ;)
        }

        AuthAction theAction = null;
        for (AuthAction action in controller.actions) {
            if (action.name.equals(actionName)) {
                theAction = action;
                break;
            }
        }

        if (!theAction) { // no authorizations required for this controller action so return true
            return true;
        }

        // We iterate through the roles attached to this action and see whether the user
        // has any of these roles assigned to them.
        for (AuthRole actionRole in theAction.roles) {
            AuthRole role = actionRole.role;
            if (user.hasRole(role)) {
                return true
            }
        }

        // If a controller action has no roles associated with it i.e. theAction.roles.size() == 0,
        // then we are going to be strict and return false, since for an action to have been added
        // to the database, it means that there should have also been an associated role, so
        // something must have gone wrong. Let's err on the side of caution.
        // This behaviour can of course be changed if necessary by uncommenting the next block of
        // code.
//        if (theAction.roles.size() == 0) {
//            return true;
//        }

        return false
    }

}